单例模式(Singleton Pattern)

什么是单例模式?

单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

单例模式有以下特点:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

介绍

意图:

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:

一个全局使用的类频繁地创建与销毁。

何时使用:

当您想控制实例数目,节省系统资源的时候。

如何解决:

判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:

构造函数是私有的。

应用实例:

  • Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理,不能两台打印机打印同一个文件。

优点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 避免对资源的多重占用(比如写文件操作)。

缺点:

  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

  • 要求生产唯一序列号。
  • WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:

getInstance()方法中需要使用同步锁synchronized(Singleton.class)防止多线程同时进入造成instance被多次实例化。

静态对象:

  • 静态对象的数据在全局是唯一的,一改都改。如果你想要处理的东西是整个程序中唯一的,弄成静态是个好方法。 非静态的东西你修改以后只是修改了他自己的数据,但是不会影响其他同类对象的数据。
  • 引用方便。直接用类名.静态方法名或者类名.静态变量名就可引用并且直接可以修改其属性值,不用get和set方法。
  • 保持数据的唯一性。此数据全局都是唯一的,修改他的任何一处地方,在程序所有使用到的地方都将会体现到这些数据的修改。有效减少多余的浪费。
  • static final用来修饰成员变量和成员方法,可简单理解为“全局常量”。对于变量,表示一旦给值就不可修改;对于方法,表示不可覆盖。

类图


常见的实现方式

饿汉式

优点:

  • 没有加锁,执行效率会提高。
  • 在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

缺点:

  • 类加载时就初始化,浪费内存,容易产生垃圾对象,jvm垃圾收集器是不会回收单例对象(静态)。——垃圾回收属个人观点。
package com.match.singleton;
/**
* 单例模式:
* 保证一个类只能产生一个对象
* 测试饿汉式单例模式(线程安全,调用效率高,但是不可以延迟加载)
* @author Match
*
*/
public class SingletonDemo1
{
//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1()
{
}
//方法没有同步锁,调用效率高!
public static SingletonDemo1 getInstance()
{
return instance;
}
}

懒汉式

此处只介绍线程安全的懒汉式。线程不安全的懒汉式只是没加同步锁(synchronized),严格意义上它并不算单例模式。

优点:

  • 具备延时加载(lazy loading)第一次调用才初始化,避免内存浪费,能够在多线程中很好的工作。

缺点:

  • 必须加锁 synchronized 才能保证单例,但加锁会影响效率。
package com.match.singleton;
/**
* 单例模式:
* 保证一个类只能产生一个对象
* 测试懒汉式单例模式(线程安全,调用效率不高,但是可以延迟加载)
* @author Match
*
*/
public class SingletonDemo2
{
//类初始化时,不初始化这个对象(延时加载,正真用的时候再创建)。
private static SingletonDemo2 instance;
private SingletonDemo2()
{
}
//方法同步,调用效率低!
public static synchronized SingletonDemo2 getInstance()
{
if(instance==null)
instance = new SingletonDemo2();
return instance;
}
}

双检锁/双重校验锁(DCL,即 double-checked locking)

注:要求JDK1.5起。

优点:

  • 具备延时加载(lazy loading)第一次调用才初始化,避免内存浪费,采用双锁机制,安全且在多线程情况下能保持高性能。

缺点:

  • 实现起来比较复杂。
package com.match.singleton;
/**
* 双重检测锁实现单例模式
* @author Match
*
*/
public class SingletonDemo3
{
private static SingletonDemo3 instance = null;

private SingletonDemo3()
{
}
public static SingletonDemo3 getInstance()
{
if(instance==null)
{
SingletonDemo3 sc;
synchronized(SingletonDemo3.class)
{
sc = instance;
if(sc==null)
{
synchronized(SingletonDemo3.class)
{
if(sc==null)
{
sc = new SingletonDemo3();
}
}
instance = sc;
}
}
}
return instance;
}
}

登记式/静态内部类——Initialization on Demand Holder (IoDH)技术

描述:

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式同样利用了classloder机制来保证初始化instance时只有一个线程,它跟饿汉式不同的是:饿汉式只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading 效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化instance。想象一下,如果实例化instance很消耗资源,所以想让它延迟加载,另外一方面,又不希望在Singleton类加载时就实例化,因为不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance 显然是不合适的。这个时候,这种方式相比饿汉式就显得很合理。

package com.match.singleton;
/**
* 测试静态内部类实现单例模式
* 这种方式:线程安全,调用效率高,并且实现了延时加载!
* @author Match
*
*/
public class SingletonDemo4
{
private SingletonDemo4()
{
}
private static class SingletonClassInstance
{
private static final SingletonDemo4 instance = new SingletonDemo4();
}
//方法没有同步,调用效率高。
public static SingletonDemo4 getInstance()
{
return SingletonClassInstance.instance;
}
}

枚举

注:要求JDK1.5起。

优点:

  • 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化,不能通过reflection attack来调用私有构造方法,支持多线程。

缺点:

  • 不具备延时加载(lazy loading)。
package com.match.singleton;
/**
* 测试枚举式单例模式(没有延时加载)
* @author Match
*
*/
public enum SingletonDemo5
{
//这个枚举元素,本身就是单例对象!
INSTANCE;
//添加自己需要的操作!
public void singletonOperation()
{
}
}

如何防止反射和反序列化漏洞

package com.match.singleton;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
* 单例模式:
* 保证一个类只能产生一个对象
* 测试懒汉式单例模式(线程安全,调用效率不高,但是可以延迟加载),如何防止反射和反序列化漏洞。
* @author Match
*
*/
public class SingletonDemo6 implements Serializable
{
private static final long serialVersionUID = 1L;
//类初始化时,不初始化这个对象(延时加载,正真用的时候再创建)。
private static SingletonDemo6 instance;
private SingletonDemo6()//私有化构造器
{
//防止反射
if(instance!=null)
{
throw new RuntimeException();
}
}
//方法同步,调用效率低!
public static synchronized SingletonDemo6 getInstance()
{
if(instance==null)
instance = new SingletonDemo6();
return instance;
}
//反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象
private Object readResolve() throws ObjectStreamException
{
return instance;
}
}
加载评论框需要翻墙